package cn.alauncher.tcpdemo;

import cn.alauncher.tcpdemo.callback.TCPSercallBack;
import cn.alauncher.tcpdemo.tool.RegularJudgment;
import cn.alauncher.tcpdemo.tool.Uni;

import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.text.SimpleDateFormat;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;




/**
 * 开启服务传入参数合法但确开启失败后会不断重新开启服务
 * 当有新的客户端连接,如果新连接一定时间没有发送指定字符串,则自动断开该连接
 * 当新的连接上后,在3次最大心跳时间都没有发送心跳数据来,则自动关闭该连接
 * 如果间检测到客户端连接参数异常,则断开该连接
 * 如果给指定客户端发送数据失败,则断开该连接
 * 可以执行Stop方法停止该服务
 * @author KK
 *
 */
public class TCPServer {

	/* 其它相关定义 */
	private Selector selector;
	private ServerSocketChannel channel;
	private ServerSocket socket;
	private ArrayList<SocketChannel> SocketClientlist = new ArrayList<SocketChannel>();
	private Map<SocketChannel, Integer> SocketClientMap1 = new HashMap<SocketChannel, Integer>();//存储首次连接的计时
	private Map<SocketChannel, Integer> SocketClientMap2 = new HashMap<SocketChannel, Integer>();//存储心跳计时
	
	private boolean IsInit = false;// 初始化成功与否标志,用于控制发送数据时判断
	private boolean iSHreat = true;// 是否开启心跳应答和自动重启tcp服务
	private boolean isstop = true;//控制服务关闭
	private Integer hreatMaxTime = 30;//最多心跳间隔,3次最多心跳间隔都没有收到心跳就移除关闭连接,单位秒
	/*暂存端口和IP,便于服务死后自动重启*/
	private int ServerPort = 10;
	private String ServerIP = "0.0.0.0"; 
	private TCPSercallBack TCPcallBack = null;
	private String HreatStr = "h";//心跳内容,会被过滤
	private String HasconStr = "cqyw";//tcp连接上发送的内容,如果连接上一段时间(默认10秒)都没有发送就断开该连接
	/**
	 * 启动TCPserver
	 * 
	 * @param IneIP
	 *            IP/也可以写"0.0.0.0"表示不绑定本地的IP,本机的任意一个IP都接收连接
	 * @param port
	 *            断口号
	 * @param callBack
	 *            TCPserver回调对象
	 * @return IsInit 是否启动成功
	 */
	public boolean Start(String IneIP, int port, TCPSercallBack callBack) {
		if(callBack != null ) {
			// 回调对象赋值
			TCPcallBack = callBack;
			if((RegularJudgment.isboolIp(IneIP) || IneIP.equals("0.0.0.0")) && RegularJudgment.isdata(String.valueOf(port))) {
				/*服务器参数赋值*/
				ServerPort = port;
				ServerIP = IneIP;
				/* 初始化一个Selector */
				try {
					selector = Selector.open();
					/* 打开通道 */
					channel = ServerSocketChannel.open();
					/* 非阻塞模式 */
					channel.configureBlocking(false);
					/* IP */
					InetAddress IPAddress;
					InetSocketAddress address;
					if (IneIP.equals("0.0.0.0")) {
						/* 绑定端口 */
						address = new InetSocketAddress(port);
					} else {
						IPAddress = InetAddress.getByName(IneIP);
						/* 绑定IP和端口 */
						address = new InetSocketAddress(IPAddress, port);
					}
					socket = channel.socket();
					socket.bind(address);
					socket.setSoTimeout(1000);//读数据超时时间
					/* 启动监听 */
					IsInit = true;// 初始化成功了标志
					isstop = true;//保证可以运行监听线程
					LisDataTread T = new LisDataTread();
					T.start();
					TCPcallBack.IniResult(true, port);
				} catch (IOException e) {
					// TODO 自动生成的 catch 块
					IsInit = false;// 初始化成功了标志
					TCPcallBack.IniResult(false, port);
					System.out.println("TCP Start erro...");
					e.printStackTrace();
				}
			}else {
				TCPcallBack.IniResult(false, port);
				System.out.println("TCP Start erro...");
			}
			
		}else {
			System.out.println("TCP Start erro...");
		}
		return IsInit;
	}

	/**
	 * 设置心跳相关参数
	 * @param conStr 连接上需要发送的内容
	 * @param hreatstr 心跳内容
	 * @param hreatmaxtime 客户端心跳最大间隔时间
	 */
	public void SetHreat(String conStr,String hreatstr,Integer hreatmaxtime) {
		iSHreat = true;
		HreatStr = hreatstr;
		HasconStr = conStr;
		if(hreatmaxtime>0){
			hreatMaxTime = hreatmaxtime;
		}
		MyHreat T = new MyHreat();
		T.start();
	}
	


	/**
	 * 停止TCPserver
	 * 
	 * 
	 */
	public void Stop() {
		isstop = false;
		IsInit = false;
		iSHreat = false;
	}

	/**
	 * 根据连接地址获取某个客户端连接
	 * @param address
	 * @return
	 */
	public SocketChannel getSocketClient(String address) {
		for(SocketChannel sc:SocketClientlist) {
			try {
				if(sc.getLocalAddress().toString().equals(address)) {
					return sc;
				}
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
		return null;
	}
	
	/**
	 * 获取当前所有的客户端连接
	 * @return List<SocketChannel>
	 */
	public List<SocketChannel> getSocketClients() {
		return SocketClientlist;
	}

	/**
	 * 向指定的客户端发送字符串数据，有哪客户端先通过getSocketClient方法获取
	 * @param msg
	 * @param SocketClient
	 * @return
	 */
	public byte TCPServerSendStrToClient(String msg, SocketChannel Sc) {
		if (IsInit  && isconnet(Sc)) {
			byte[] sendbuffer;
			try {
				sendbuffer = msg.getBytes("utf-8");
				TCPServerSendBytesToClient(sendbuffer, 0, sendbuffer.length, Sc);
				return 0;
			} catch (UnsupportedEncodingException e) {
				// TODO 自动生成的 catch 块
				destroyClient(Sc);
				e.printStackTrace();
				return -1;
			}
		} else {
			return -2;
		}
	}

	/**
	 * 向指定的客户端发送byte数组数据，有哪客户端先通过getSocketClient方法获取
	 * 
	 * @param SendBuff
	 *            待发送的byte数组
	 * @param off
	 *            待发送数组起始地址
	 * @param length
	 *            待发送数组数据个数
	 * @param SocketClient
	 *            接收数据的客户端
	 */
	public byte TCPServerSendBytesToClient(byte[] SendBuff, int off, int length, SocketChannel SocketClient) {
		if (IsInit  && isconnet(SocketClient)) {
			try {
				ByteBuffer buffer = ByteBuffer.wrap(SendBuff, off, length);
				SocketClient.write(buffer);
				return 0;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				destroyClient(SocketClient);
				e.printStackTrace();
				return -1;
			} // 把数据发送出去
		} else {
			destroyClient(SocketClient);
			return -2;
		}

	}

	/**
	 * 向所有tcp客户端发送字符串数据
	 * 
	 * @param msg
	 *            待发送的字符串
	 * @return byte 为0表示发送成功，为-1表示发送失败，为-2表示还没执行TCPsever初始化
	 */
	public byte TCPServerSendStrALL(String msg) {
		if (IsInit) {
			byte[] sendbuffer;
			try {
				sendbuffer = msg.getBytes("utf-8");
				TCPServersendbyteDataALL(sendbuffer, 0, sendbuffer.length);
				return 0;
			} catch (UnsupportedEncodingException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
				return -1;
			}
		} else {
			return -2;
		}
	}

	/**
	 * 关闭一个连接
	 * @param sc
	 */
	public void destroyClient(SocketChannel sc) {
		try {
			boolean rs1 = SocketClientlist.remove(sc);
			Integer rs2 = SocketClientMap1.remove(sc);
			Integer rs3 = SocketClientMap2.remove(sc);
			/*任意一个移除成功,说明该sc对象都是这个tcpserver的,都需要关闭.保证每个连接有且仅有连接断开一次*/
			if(rs1 || rs2!=null || rs3 != null) {
				if(isconnet(sc)) {
					TCPcallBack.Disconnected(sc, sc.getRemoteAddress().toString());
					sc.close();
				}
			}
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		
	}
	
	/**
	 * 向所有tcp客户端发送数据帧数据
	 * 
	 * @param SendBuff
	 *            待发送数据帧数组
	 * @param off
	 *            数组起始位置
	 * @param length
	 *            要发送数据个数
	 * @return byte 为0表示发送成功，为-1表示发送失败，为-2表示还没执行TCPsever初始化
	 */
	public byte TCPServersendbyteDataALL(byte[] SendBuff, int off, int length) {
		if (IsInit) {
			SocketChannel sctemp = null;
			try {
				ByteBuffer byteBuffer = ByteBuffer.allocate(SendBuff.length);
				byteBuffer.put(SendBuff);
				byteBuffer.flip();// 移动到ByteBuffer第一位
				for (SocketChannel sc : SocketClientlist) {
					sctemp = sc;
					sc.write(byteBuffer);
					byteBuffer.flip();
				}
				return 0;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				destroyClient(sctemp);
				e.printStackTrace();
				return -1;
			} // 把数据发送出去
		} else {
			return -2;
		}
	}

	private boolean isconnet(SocketChannel sc) {
		boolean rs = !sc.isBlocking() && sc.isConnected() && !sc.isConnectionPending() 
				&& sc.isOpen() && sc.isRegistered();
		try {
			sc.getLocalAddress();
			sc.getRemoteAddress();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			rs  = false;
			destroyClient(sc);
			e.printStackTrace();
		}
		return rs;
	}

	/**
	 * 开辟一个线程监视TCP数据
	 * 
	 * @author zhao
	 */
	private class LisDataTread extends Thread {
		public void run() {
			/* 注册接收事件 */
			try {
				channel.register(selector, SelectionKey.OP_ACCEPT);
				/* 无限循环 */
				while (isstop && IsInit ) {
					ArrayList<SocketChannel> SocketClientTemp = new ArrayList<SocketChannel>();
					for (SocketChannel sc : SocketClientlist) {
						if (!isconnet(sc)) {
							SocketClientTemp.add(sc);
						}
					}
					for (SocketChannel sc : SocketClientTemp) {
						destroyClient(sc);
					}
					/* 轮询事件 */
					if (selector.isOpen()) {
						selector.select();
						Iterator<SelectionKey> iter = selector.selectedKeys().iterator();//这里会阻塞
						if (selector.isOpen()) {
							while (iter.hasNext()) {
								SelectionKey key = (SelectionKey) iter.next();
								iter.remove();
								/* 事件分类处理 */
								if (key.isAcceptable()) {
									ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
									SocketChannel sc = ssc.accept();
									sc.configureBlocking(false);// 非阻塞模式
									sc.register(selector, SelectionKey.OP_READ);
									SocketClientlist.add(sc);// 把新连接的终端保存起来
									List<String> IPRs = Uni.getIPbystr(sc.getRemoteAddress().toString());
									List<String> IPLs = Uni.getIPbystr(sc.getLocalAddress().toString());
									System.out.println("RemoteAddress:"+IPRs.get(0)+",LocalAddress:"+IPLs.get(0));
									if(!IPRs.get(0).equals(IPLs.get(0))) {
										SocketClientMap1.put(sc, 0);//新连接开始加入是否有回复计时
									}
									TCPcallBack.Hasconnect(sc);// 有客户端连接上了执行回调回调连接方法
								} else if (key.isReadable()) {
									SocketChannel sc = (SocketChannel) key.channel();
									try {
										int recvCount = 0;//记录客户端发送来了多少内容
										List<ByteBuffer> buffers = new ArrayList<>();
										while (true) {
											/* 缓冲区大小 */
											ByteBuffer buffertemp = ByteBuffer.allocate(10240);//1次读取10KB
											int read = sc.read(buffertemp);
											if (read <= 0) {
												if(recvCount==0) {
													recvCount = read;
												}
												break;
											}
											buffers.add(buffertemp);
											recvCount = recvCount+read;
										}
										if (recvCount >= 0) {
											ByteBuffer buffer  = ByteBuffer.allocate(recvCount);
											/*把数据从List放入ByteBuffer*/
											for(ByteBuffer bvalue:buffers) {
												buffer.put(bvalue.array(), 0, bvalue.position());
											}
											/*把数据从ByteBuffer放入byte数组*/
											byte[] arr = Arrays.copyOf(buffer.array(), recvCount);
											boolean isHreatCall = false;
											if (iSHreat) {
												byte[] arrtemp1 = new byte[1];
												byte[] arrtemp2 = new byte[1];
												if(recvCount > HreatStr.length()) {
													System.arraycopy(arr, 0, arrtemp1, 0, 1);
													System.arraycopy(arr, recvCount-1, arrtemp2, 0, 1);
												}
												if((recvCount == HreatStr.length() && HreatStr.equals(new String(arr))) || HreatStr.equals(new String(arrtemp1)) || HreatStr.equals(new String(arrtemp2)) ) {
													SocketClientMap2.put(sc, 0);
													TCPServerSendBytesToClient(HreatStr.getBytes(), 0, HreatStr.length(), sc);// 心跳回复
													isHreatCall  = true;
													Date day=new Date();//时间戳获取
													SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
													System.out.println("sc:"+sc+",Hreatstr:"+new String(arr)+"----"+df.format(day));
												}
											}
											if(!isHreatCall || recvCount != HreatStr.length()) {
												if(Arrays.equals(HasconStr.getBytes(),arr)) {
													Integer rs = SocketClientMap1.remove(sc);
													if(rs != null) {
														SocketClientMap2.put(sc, 0);
													}else {
														TCPcallBack.MssageArrived(arr, sc.getRemoteAddress().toString());// 数据到达执行方法
													}
												}
												else {
													TCPcallBack.MssageArrived(arr, sc.getRemoteAddress().toString());// 数据到达执行方法
												}
											}
											
											buffer.flip();
											buffer.clear();
										} else if (recvCount == -1) {
											key.cancel();
											destroyClient(sc);
										}
									} catch (IOException e) {
										e.printStackTrace();
										destroyClient(sc);
									}
								}else {
									if(!key.isConnectable() && !key.isValid()) {
										SocketChannel sc = (SocketChannel) key.channel();
										destroyClient(sc);
									}
								}
							}
						} else {
							break;
						}
					} else {
						break;
					}
				}
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			/*tcp服务停掉,如果是意外停止则自动重启*/
			stopServer();
			System.out.println("--------------");
		}

	}
	
	/**
	 * 停止服务
	 */
	private void stopServer() {
		if(isstop) {
			IsInit  = false;//如果跳出循环isstop为true,就是当isstop为true即为非人为停止就需要做重启服务
		}
		try {
			socket.close();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		try {
			channel.close();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		try {
			selector.close();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		SocketClientlist.clear();
		SocketClientMap1.clear();
		SocketClientMap2.clear();
	}
	
	private class MyHreat extends Thread {
		public void run() {
			List<SocketChannel> MapListtemp = new ArrayList();
			ArrayList<SocketChannel> SocketClientTemp = new ArrayList<SocketChannel>();
			while(iSHreat && isstop) {
				if(!IsInit) {
					if(TCPcallBack != null && (RegularJudgment.isboolIp(ServerIP) || ServerIP.equals("0.0.0.0")) && RegularJudgment.isdata(String.valueOf(ServerPort))) {
						Start(ServerIP,ServerPort,TCPcallBack);
					}
				}
				
				for(Entry<SocketChannel, Integer> MapSet:SocketClientMap1.entrySet()) {
					Integer value = MapSet.getValue();
					/*连接上后10秒都没有指定字符串到达就移除该连接*/
					if(value>10) {
						MapListtemp.add(MapSet.getKey());
					}
					else {
						value++;
						MapSet.setValue(value);						
					}
				}
				for(SocketChannel value:MapListtemp) {
					destroyClient(value);
				}
				MapListtemp.clear();
				
				for(Entry<SocketChannel, Integer> MapSet:SocketClientMap2.entrySet()) {
					Integer value = MapSet.getValue();
					/*一定时间没有心跳回复就移除*/
					if(value>hreatMaxTime*3) {
						MapListtemp.add(MapSet.getKey());
					}
					else {
						value++;
						MapSet.setValue(value);	
					}
				}
				for(SocketChannel value:MapListtemp) {
					destroyClient(value);
				}
				MapListtemp.clear();
				
				
				for (SocketChannel sc : SocketClientlist) {
					if (!isconnet(sc)) {
						SocketClientTemp.add(sc);
					}
				}
				for (SocketChannel sc : SocketClientTemp) {
					destroyClient(sc);
				}
				SocketClientTemp.clear();
				
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
			}
		}
	}

}